package org.gdstash.db;

import org.gdstash.file.ARZRecord;
import org.gdstash.util.GDMsgFormatter;
import org.gdstash.util.GDMsgLogger;

import java.sql.*;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

public class DBSkillTree {
    public static final String TABLE_NAME = "GD_SKILLTREE";
    private static final int ROW_SKILLTREE_ID = 1;
    private static ConcurrentHashMap<String, DBSkillTree> hashBuffer;
    private String skillTreeID;
    private List<DBSkillTreeAlloc> skillIDs;

    public DBSkillTree() {
        this.skillTreeID = null;
        this.skillIDs = new LinkedList<DBSkillTreeAlloc>();
    }

    public DBSkillTree(final String skillTreeID) {
        this();
        this.skillTreeID = skillTreeID;
    }

    private DBSkillTree(final ARZRecord record) {
        this.skillTreeID = record.getFileName();
        this.skillIDs = record.getMasterySkillList();
    }

    static {
        DBSkillTree.hashBuffer = new ConcurrentHashMap<String, DBSkillTree>();
    }

    public String getSkillTreeID() {
        return this.skillTreeID;
    }

    public List<String> getSkillIDList() {
        final List<String> list = new LinkedList<String>();
        for (final DBSkillTreeAlloc alloc : this.skillIDs) {
            list.add(alloc.getSkillID());
        }
        return list;
    }

    public List<DBSkillTreeAlloc> getSkillAllocList() {
        return this.skillIDs;
    }

    public static void clearBuffer() {
        DBSkillTree.hashBuffer.clear();
    }

    public static void createTables() throws SQLException {
        final String dropTable = "DROP TABLE GD_SKILLTREE";
        final String createTable = "CREATE TABLE GD_SKILLTREE (SKILLTREE_ID      VARCHAR(256) NOT NULL, PRIMARY KEY (SKILLTREE_ID))";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final Statement st = conn.createStatement()) {
                if (GDDBUtil.tableExists(conn, "GD_SKILLTREE")) {
                    st.execute(dropTable);
                }
                st.execute(createTable);
                st.close();
                conn.commit();
                DBSkillTreeAlloc.createTable(conn);
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {"GD_SKILLTREE"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_CREATE_TABLE", args);
                GDMsgLogger.addError(msg);
                throw ex;
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    public static void delete(final String skillTreeID) throws SQLException {
        final String deleteEntry = "DELETE FROM GD_SKILLTREE WHERE SKILLTREE_ID = ?";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final PreparedStatement ps = conn.prepareStatement(deleteEntry)) {
                ps.setString(1, skillTreeID);
                ps.executeUpdate();
                ps.close();
                DBSkillTreeAlloc.delete(conn, skillTreeID);
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {skillTreeID, "GD_SKILLTREE"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_DEL_TABLE_BY_ID", args);
                GDMsgLogger.addError(msg);
                GDMsgLogger.addError(ex);
                throw ex;
            } finally {
                conn.setAutoCommit(auto);
            }
        } catch (SQLException ex2) {
            throw ex2;
        }
    }

    public static void insert(final ARZRecord record) throws SQLException {
        final DBSkillTree entry = get(record.getFileName());
        if (entry != null) {
            return;
        }
        final DBSkillTree skillTree = new DBSkillTree(record);
        if (skillTree.skillIDs == null) {
            return;
        }
        if (skillTree.skillIDs.isEmpty()) {
            return;
        }
        final String insert = "INSERT INTO GD_SKILLTREE VALUES (?)";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final PreparedStatement ps = conn.prepareStatement(insert)) {
                ps.setString(1, skillTree.skillTreeID);
                ps.executeUpdate();
                ps.close();
                conn.commit();
                DBSkillTreeAlloc.insert(conn, skillTree);
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {record.getFileName(), "GD_SKILLTREE"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_INS_TABLE_BY_ID", args);
                GDMsgLogger.addLowError(msg);
                GDMsgLogger.addLowError(ex);
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    public static List<DBSkillTree> getMasteryTrees() {
        final List<DBSkillTree> list = new LinkedList<DBSkillTree>();
        final List<DBEnginePlayerMasteries> masteries = DBEnginePlayerMasteries.get();
        if (masteries == null) {
            return list;
        }
        for (final DBEnginePlayerMasteries mt : masteries) {
            final DBSkillTree tree = get(mt.getSkillTreeID());
            if (tree != null) {
                list.add(tree);
            }
        }
        return list;
    }

    public static DBSkillTree get(final String skillTreeID) {
        DBSkillTree tree = null;
        tree = DBSkillTree.hashBuffer.get(skillTreeID);
        if (tree == null) {
            tree = getDB(skillTreeID);
        }
        return tree;
    }

    private static DBSkillTree getDB(final String skillTreeID) {
        DBSkillTree skillTree = new DBSkillTree();
        final String command = "SELECT * FROM GD_SKILLTREE WHERE SKILLTREE_ID = ?";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            ps.setString(1, skillTreeID);
            try (final ResultSet rs = ps.executeQuery()) {
                final List<DBSkillTree> list = wrap(rs);
                if (list.isEmpty()) {
                    skillTree = null;
                } else {
                    skillTree = list.get(0);
                }
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            final Object[] args = {skillTreeID, "GD_SKILLTREE"};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_READ_TABLE_BY_ID", args);
            GDMsgLogger.addError(msg);
            GDMsgLogger.addError(ex2);
        }
        return skillTree;
    }

    public static DBSkillTree getBySkillID(final String skillID) {
        final DBEnginePlayer player = DBEnginePlayer.get();
        if (player == null) {
            return null;
        }
        final List<DBEnginePlayerMasteries> masteries = player.getMasteryTreeList();
        final List<DBSkillTreeAlloc> treeIDs = DBSkillTreeAlloc.getAllocBySkillID(skillID);
        if (masteries == null) {
            return null;
        }
        if (treeIDs == null) {
            return null;
        }
        if (treeIDs.isEmpty()) {
            return null;
        }
        String id = null;
        for (final DBSkillTreeAlloc alloc : treeIDs) {
            if (DBEnginePlayerMasteries.containsSkillTreeID(masteries, alloc.getTreeID())) {
                id = alloc.getTreeID();
                break;
            }
        }
        if (id == null) {
            return null;
        }
        final DBSkillTree skillTree = get(id);
        return skillTree;
    }

    private static List<DBSkillTree> wrap(final ResultSet rs) throws SQLException {
        final LinkedList<DBSkillTree> list = new LinkedList<DBSkillTree>();
        while (rs.next()) {
            final DBSkillTree skillTree = new DBSkillTree();
            skillTree.skillTreeID = rs.getString(1);
            final DBSkillTree buff = DBSkillTree.hashBuffer.get(skillTree.skillTreeID);
            if (buff != null) {
                list.add(buff);
            } else {
                skillTree.skillIDs = DBSkillTreeAlloc.getAllocByTreeID(skillTree.skillTreeID);
                list.add(skillTree);
                DBSkillTree.hashBuffer.put(skillTree.skillTreeID, skillTree);
            }
        }
        return list;
    }

    public DBSkill getMasterySkill() {
        if (this.skillIDs == null) {
            return null;
        }
        for (final DBSkillTreeAlloc alloc : this.skillIDs) {
            final DBSkill skill = DBSkill.get(alloc.getSkillID());
            if (skill != null && skill.isMastery()) {
                return skill;
            }
        }
        return null;
    }
}
